/*
 * Copyright 2011 David de Mingo
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.shalma.persistence;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

import org.shalma.Service;
import org.shalma.internal.persistence.EntityTupleBinding;
import org.shalma.model.Entity;

import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Environment;
import com.sleepycat.je.EnvironmentConfig;
import com.sleepycat.je.LockMode;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.SecondaryConfig;
import com.sleepycat.je.SecondaryDatabase;
import com.sleepycat.je.SecondaryKeyCreator;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.TransactionConfig;

public class Persistence implements Service {

	private static final String UTF_8 = "UTF-8";

	private Environment environment;

	private Database primaryDatabase;
	private Map<String, SecondaryDatabase> secondaryDatabases;

	private EntityTupleBinding entityBinding;

	public void start() throws DatabaseException {

		EnvironmentConfig envConfig = new EnvironmentConfig();
		envConfig.setReadOnly(false);
		envConfig.setAllowCreate(true);

		File envHome = new File("target/database");
		if (!envHome.exists())
			envHome.mkdirs();

		environment = new Environment(envHome, envConfig);

		DatabaseConfig dbConfig = new DatabaseConfig();
		dbConfig.setAllowCreate(true);

		// TODO database name -> id
		primaryDatabase = environment.openDatabase(null, "Entity", dbConfig);

		secondaryDatabases = new HashMap<String, SecondaryDatabase>();

		entityBinding = new EntityTupleBinding();
	}

	public void stop() throws DatabaseException {

		if (primaryDatabase != null)
			primaryDatabase.close();

		if (environment != null) {
			environment.cleanLog();
			environment.close();
		}
	}

	public Entity get(Transaction transaction, Entity.Key id)
			throws DatabaseException {

		Entity entity = null;

		DatabaseEntry key = new DatabaseEntry(id.getEntityId());
		DatabaseEntry data = new DatabaseEntry();

		OperationStatus status = primaryDatabase.get(transaction, key, data,
				LockMode.DEFAULT);

		if (status == OperationStatus.SUCCESS)
			entity = (Entity) entityBinding.entryToObject(data);

		return entity;
	}

	public void put(Transaction transaction, Entity entity)
			throws DatabaseException {

		DatabaseEntry data = new DatabaseEntry();
		entityBinding.objectToEntry(entity, data);

		primaryDatabase.put(transaction, new DatabaseEntry(entity.getKey()
				.getEntityId()), data);
	}

	public void delete(Transaction transaction, Entity.Key id)
			throws UnsupportedEncodingException, DatabaseException {

		primaryDatabase
				.delete(transaction, new DatabaseEntry(id.getEntityId()));
	}

	public Transaction beginTransaction() throws DatabaseException {

		return environment.beginTransaction(null, TransactionConfig.DEFAULT);
	}

	SecondaryDatabase getSecondaryDatabase(String name)
			throws DatabaseException {

		SecondaryDatabase secondaryDatabase = secondaryDatabases.get(name);

		// TODO sync
		if (secondaryDatabase == null) {

			SecondaryConfig config = new SecondaryConfig();
			config.setAllowCreate(true);
			config.setSortedDuplicates(true);
			config.setAllowPopulate(true);
			config.setKeyCreator(new PropertyKeyCreator());

			secondaryDatabase = environment.openSecondaryDatabase(null, name,
					primaryDatabase, config);
			secondaryDatabases.put(name, secondaryDatabase);
		}

		return secondaryDatabase;
	}

	public static class PropertyKeyCreator implements SecondaryKeyCreator {

		private byte[] propertyId;

		@Override
		public boolean createSecondaryKey(SecondaryDatabase secondary,
				DatabaseEntry key, DatabaseEntry data, DatabaseEntry result)
				throws DatabaseException {

			byte[] propertyValue = EntityTupleBinding.getPropertyAsBytes(
					propertyId, data);

			if (propertyValue != null) {
				result.setData(propertyValue);
				return true;
			} else
				return false;
		}
	}
}
